Cognito 用户池:如何使用刷新令牌刷新访问令牌

Posted

技术标签:

【中文标题】Cognito 用户池:如何使用刷新令牌刷新访问令牌【英文标题】:Cognito User Pool: How to refresh Access Token using Refresh Token 【发布时间】:2016-09-23 09:39:30 【问题描述】:

我正在使用 Cognito 用户池对系统中的用户进行身份验证。成功的身份验证会提供 ID 令牌 (JWT)访问令牌 (JWT)刷新令牌。 documentation here,明确提到刷新令牌可用于刷新访问令牌,但没有提及如何。 我的问题是,一旦我的 Access Token 过期,如何使用存储的刷新令牌再次刷新我的访问令牌?

我搜索了 javascript SDK,但找不到任何方法来做同样的事情。我肯定错过了一些东西。

我还考虑通过一个 Lambda 函数来执行此操作,该函数接受访问令牌和刷新令牌并以刷新的访问令牌进行响应。如果有人能对此有所了解,那就太好了。

【问题讨论】:

AWS的ios-SDK如何刷新token? 【参考方案1】:

如果您处于 Cognito Javascript SDK 无法满足您的需求的情况,您仍然可以在 SDK source 中查看它如何处理刷新过程:

您可以在 refreshSession 中看到,调用 Cognito InitiateAuth 端点时为 REFRESH_TOKEN_AUTH 设置了 AuthFlow 值,并且传入了一个对象作为 AuthParameters 值。

该对象需要进行配置以满足您的用户池的需要。具体来说,如果您的目标 App 客户端 ID 具有关联的 App 客户端密码,您可能必须传入您的 SECRET_HASH。为与 Javascript SDK 一起使用而创建的用户池客户端应用目前不能包含客户端密码,因此不需要 SECRET_HASH 来连接它们。

另一个可能会让您陷入循环的警告是,如果您的用户池设置为记住设备,并且您没有将DEVICE_KEYREFRESH_TOKEN 一起传递。如果您传入 RefreshToken 而没有同时传入 DeviceKey,则 Cognito API 当前会返回 "Invalid Refresh Token" 错误。即使您传入有效的RefreshToken,也会返回此错误。上面链接的线程说明了这一点,尽管我确实希望 AWS 在未来更新他们的错误处理以不那么神秘。

正如该线程中所讨论的,如果您将AdminInitiateAuth 与ADMIN_NO_SRP_AUTH 一起使用,则您的成功身份验证响应负载当前不包含NewDeviceMetadata;这意味着您在尝试刷新令牌时不会有任何 DeviceKey 可传递。

我的应用需要在 Python 中实现,所以这里有一个对我有用的示例:

def refresh_token(self, username, refresh_token):
    try:
        return client.initiate_auth(
            ClientId=self.client_id,
            AuthFlow='REFRESH_TOKEN_AUTH',
            AuthParameters=
                'REFRESH_TOKEN': refresh_token,
                'SECRET_HASH': self.get_secret_hash(username)
                # Note that SECRET_HASH is missing from JSDK
                # Note also that DEVICE_KEY is missing from my example
            
        )
    except botocore.exceptions.ClientError as e:
        return e.response

【讨论】:

非常感谢@afilbert 提供的信息丰富的帖子。我没有传入 DEVICE_KEY 并且收到“无效刷新令牌”错误,这是 AWS 方面的一个非常糟糕的错误处理。 不错的答案,@afilbert--upvoted。 (不幸的是,SO 用户几年前就开始将投票视为个人花钱。;) 非常感谢您提供有关设备密钥的指针! (我禁用了设备跟踪,它又可以正常工作了)。 @nueverest SECRET_HASH必需的,如果用户池应用程序已使用App client secret 定义,但它们不是一回事。 boto3 文档将SecretHash 描述如下:“使用用户池客户端的密钥和用户名加上消息中的客户端 ID 计算的密钥散列消息身份验证代码 (HMAC)。” boto3.readthedocs.io/en/latest/reference/services/…如果您在 SO 上发布有关如何创建 SECRET_HASH 的问题,我很乐意分享我的解决方案作为答案。 仅供参考,为了能够在ADMIN_NO_SRP_AUTH 之后调用REFRESH_TOKEN_AUTH,您必须禁用设备跟踪。这个不是很清楚,希望对大家有帮助!【参考方案2】:

JavaScript SDK 在内部处理令牌的刷新。当您调用 getSession 获取令牌时,如果没有任何有效的缓存访问和 id 令牌,SDK 会使用刷新令牌来获取新的访问和 id 令牌。它调用用户认证,要求用户提供用户名和密码,只有当刷新令牌也过期时。

【讨论】:

Mahesh,你能给出代码示例吗? 嗨 Mahesh(以及其他可能需要此功能的人)请参阅此处的链接 github.com/aws/amazon-cognito-identity-js 特别是用例 16。返回的会话包含令牌。 在此链接中,查看其文档的“获取当前用户”部分。当您调用 cognitoUser.getSession() 时,sdk 将自动为您刷新您的访问/ ID 令牌,在您的 API 中调用经过身份验证的路由之前最好这样做。 docs.aws.amazon.com/cognito/latest/developerguide/… 谢谢马赫什。任何想确认上述评论的人,请访问github.com/aws-amplify/amplify-js/blob/master/packages/…【参考方案3】:

使用 amazon-cognito-identity-js 浏览器 SDK 刷新会话;它主要是为你做的,除非你做一些不寻常的事情,否则你不需要直接处理刷新令牌。以下是您需要了解的内容:

假设您已像这样实例化用户池:

const userPool = new AmazonCognitoIdentity.CognitoUserPool(
  UserPoolId: USER_POOL_ID,
  ClientId: USER_POOL_CLIENT_ID
);

要查找最后一个经过身份验证的用户名,您可以这样做:

const cognitoUser = cognitoUserPool.getCurrentUser();

如果它找到一个,cognitoUser 将是非空的,你可以这样做,如果需要,它将在后台刷新你的令牌:

cognitoUser.getSession(function(err, data) 
  if (err) 
    // Prompt the user to reauthenticate by hand...
   else 
    const cognitoUserSession = data;
    const yourIdToken = cognitoUserSession.getIdToken().jwtToken;
    const yourAccessToken = cognitoUserSession.getAccessToken().jwtToken;
  
);

如果您不希望这些令牌保留在本地存储中,您可以:

cognitoUser.signOut();

它的工作方式是,在成功验证后,浏览器将存储您的 JWT 令牌,包括该刷新令牌。默认情况下,它将这些存储在浏览器的本地存储中,但您可以根据需要提供自己的存储对象。默认情况下,刷新令牌的有效期为 30 天,但它是您的 UserPoolClient 的一个属性 (RefreshTokenValidity),您可以更改它。当您执行上述操作时,getSession() 将首先查看您存储的令牌是否存在并且仍然有效;如果没有,它会尝试使用它在那里找到的任何 refreshToken 来验证你进入一个新的会话。

http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html 的文档表明 iOS 和 android SDK 将为您执行此操作,但我没有使用过这些,因此无法保证。

【讨论】:

【参考方案4】:

我在 Javascript 中也一直在努力解决这个问题。这是我的解决方案,它基于https://github.com/aws/amazon-cognito-identity-js 但它不依赖于存储,因此您可以根据需要在 lambda 函数中使用它。 编辑:修复代码,感谢 Crayons

const userPool = new AWSCognito.CognitoUserPool(
  UserPoolId: <COGNITO_USER_POOL>,
  ClientId: <COGNITO_APP_ID>
)

userPool.client.makeUnauthenticatedRequest('initiateAuth', 
  ClientId: <COGNITO_APP_ID>,
  AuthFlow: 'REFRESH_TOKEN_AUTH',
  AuthParameters: 
    'REFRESH_TOKEN': <REFRESH_TOKEN> // client refresh JWT
  
, (err, authResult) => 
  if (err) 
     throw err
  
  console.log(authResult) // contains new session
)

【讨论】:

这行得通,但是,AuthParameters 格式应该是"REFRESH_TOKEN": &lt;your_refresh_token&gt;。另请注意,如果您启用了设备跟踪,则必须在 AuthParameters 中传递客户端设备密钥或关闭设备跟踪。【参考方案5】:

这里是一个示例,说明如何使用 Node.js 在服务器端使用 JavaScript。

const AccessToken = new CognitoAccessToken( AccessToken: tokens.accessToken );
const IdToken = new CognitoIdToken( IdToken: tokens.idToken );
const RefreshToken = new CognitoRefreshToken( RefreshToken: tokens.refreshToken );

const sessionData = 
  IdToken: IdToken,
  AccessToken: AccessToken,
  RefreshToken: RefreshToken
;
const userSession = new CognitoUserSession(sessionData);

const userData = 
  Username: email,
  Pool: this.userPool
;

const cognitoUser = new CognitoUser(userData);
cognitoUser.setSignInUserSession(userSession);

cognitoUser.getSession(function (err, session)  // You must run this to verify that session (internally)
  if (session.isValid()) 
    // Update attributes or whatever else you want to do
   else 
    // TODO: What to do if session is invalid?
  
);

您可以在我的博文How to authenticate users with Tokens using Cognito 中看到一个完整的工作示例。

【讨论】:

【参考方案6】:

如果您有一个刷新令牌,那么您只需向 Cognito 发出这个简单的 POST 请求即可获得新的访问和 ID 令牌:

POST https://mydomain.auth.us-east-1.amazoncognito.com/oauth2/token >
Content-Type='application/x-www-form-urlencoded'
Authorization=Basic base64(client_id + ':' + client_secret)

grant_type=refresh_token&
client_id=YOUR_CLIENT_ID&
refresh_token=YOUR_REFRESH_TOKEN

您将收到以下回复:

HTTP/1.1 200 OK
Content-Type: application/json


   "access_token":"eyJz9sdfsdfsdfsd", 
   "id_token":"dmcxd329ujdmkemkd349r",
   "token_type":"Bearer", 
   "expires_in":3600

请记住,授权标头应包含上述计算的 base64。

【讨论】:

如果它确实返回了 refresh_token,这将是一个简洁的解决方案,但事实并非如此。您提供的示例来自 AWS 文档 (docs.aws.amazon.com/cognito/latest/developerguide/…),但它们也提供了注释:刷新令牌在规范中定义,但当前未实现从令牌端点返回。 想法已经改变 :) 令牌端点仅在 grant_type 为 authentication_code 时才返回 refresh_token。 这个答案是正确的!我更新了 HTTP 响应以反映它不返回新的刷新令牌的事实。刷新令牌只会给您一个新的访问令牌和一个新的 id 令牌。默认情况下,用于更新它们的刷新令牌有效期为 30 天 - 如果您没有更改它。而且刷新令牌本身无法更新,但您可以将其有效期延长至 10 年(不过我不建议这样做)。【参考方案7】:

使用 NodeJS aws-sdk 和一点 Promise,您可以使用 Refresh TokeninitiateAuth 进行 await 身份验证,如下所示:

const CognitoIdentityServiceProvider = require('aws-sdk');

const initiateAuth = (ClientId, REFRESH_TOKEN, DEVICE_KEY) =>
    new Promise((resolve, reject) => 
        const CISP = new CognitoIdentityServiceProvider();

        CISP.initiateAuth(
            
                ClientId, // Cognito App Client Id
                AuthFlow: 'REFRESH_TOKEN_AUTH',
                AuthParameters: 
                    REFRESH_TOKEN,
                    DEVICE_KEY
                
            ,
            (err, data) => 
                if (err) 
                    return reject(err);
                

                resolve(data);
            
        );
    );

// ------ Usage ------ //

(async () => 
    const tokens = await initiateAuth('mY4pps3cR3T', '<R3FR3SHT0K3N>');

    console.log('Tokens', tokens);

    const AuthenticationResult: AccessToken, IdToken, ExpiresIn, TokenType = tokens;
)()

请记住,如果启用了设备跟踪,您应该传递设备密钥,否则您可能会收到无效的刷新令牌 错误。

【讨论】:

以上是关于Cognito 用户池:如何使用刷新令牌刷新访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何从 AWS cognito 授权代码获取访问和刷新令牌

如何从 Cognito 获取 Apple 刷新令牌?

如何修改 AWS Cognito 用户池的访问令牌和身份令牌的到期时间

如何使用访问和刷新令牌返回自定义数据以识别 Django Rest Framework 简单 JWT 中的用户?

使用 amazon cognito 域 UI(从用户池生成)登录后未获得 cognito 访问令牌?

AWS Cognito:生成令牌并在使用 amazon-cognito-identity-js SDK 刷新后