I've successfully included a jwt auth token in my application and able to restrict access to the endpoints. I want to embed the user's ID in the jwt token, but I'm struggling how to implement the jwt verifier in Ktor.
I create a token for the client something like this:
val token = JWT.create().withAudience(audience).withIssuer(issuer).withClaim("userId", "XXX").sign(algorithm)
The route is setup like this. The authentication {} block is run on server startup and does not allow creating a verifier with the userId.
This was derived from the jwt sample:
route("/api") {
authentication {
val jwtVerifier = makeJwtVerifier(issuer, audience)
jwtAuthentication(jwtVerifier, realm) { credential ->
if (credential.payload.audience.contains(audience))
JWTPrincipal(credential.payload)
else
null
}
handle {
// Handle jwt succcess here
}
}
private fun makeJwtVerifier(issuer: String, audience: String): JWTVerifier = JWT
.require(algorithm)
.withAudience(audience)
.withIssuer(issuer)
.build()
What is the correct way to do this? I understand I'd need to create a verifier for each request, but don't really know where to do this nor if that is desirable.
You should implement it in here. You don't need a verifier that checks user ids.
jwtAuthentication(jwtVerifier, realm) { credential ->
if (credential.payload.audience.contains(audience))
val userId = credential.payload.claims["userId"].asString()
// check if user exists ... if you want
JWTPrincipal(credential.payload)
else
null
}
Related
When the user submits his credentials to my api, I call an external api to authenticate the user. After that, a token gets generated on the external api and will be sent to me. For that I implemented the HandleAuthenticateAsync function from the AuthenticationHandler:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//before this: make call to external api to get the access token
var claims = new[] {
new Claim(ClaimTypes.Name, submittedToken),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
I have implemented a custom AuthorizationHandler which I want to check for the access token that you got when you successfully authenticate. Note that the actual authentication and authorization is done by an external api which is a custom implementation. Here is the function:
public class IsAuthorizedRequirement : AuthorizationHandler<IsAuthorizedRequirement>, IAuthorizationRequirement
{
public AuthenticateHandlerHelperFunctions AuthenticateHandlerHelper;
public IsAuthorizedRequirement()
{
AuthenticateHandlerHelper = new AuthenticateHandlerHelperFunctions();
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAuthorizedRequirement requirement)
{
if(!context.User.HasClaim(c => c.Type == ClaimTypes.Name))
{
context.Fail();
return;
}
var token = context.User.FindFirst(c => c.Type == ClaimTypes.Name).Value;
if (!string.IsNullOrEmpty(token))
{
context.Fail();
return;
}
var checkedToken = await AuthenticateHandlerHelper.CheckAccessToken(token);
if (checkedToken == null)
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}
The CheckAccessToken function makes a simple HTTP Post Request to the external Api where I get back if the token is still valid or not. Is this a valid implementation especially when multiple users are using this? Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request? Currently I have no way to test this so I just wanted to know if I am on the right track for this. Thanks
Is this a valid implementation especially when multiple users are using this?
I strongly stand against this approach. Implementation like this mean you would call external API for validate and generate token(or cookie or any form of authenticated certificate) on external server for each and any of your request(which require authentication).
It's could be consider acceptable if we have some special cases on just some endpoints. But for the whole API/Web server. Please don't use this approach.
Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request?
They'll create for each request. As I can see in the code there are no part for generate cookie or some form of retaining user information for the client to attach next request afterward.
The problem is related to WebSocket auth using JWT.
I know how to create a regular API call using route (HTTP requests), authenticate it using JWT and is working fine.
The thing that is bothering me is how to use JWT when working with WebSockets? The authenticate will not work with JWT like with HTTP requests. I have researched how but hit a dead end.
The question is how do i authenticate WebSocket routes using JWT? Any pointers are appreciated or if i am missing the point how it works would you be kind to explain what I am understanding wrong.
I figured out that wrapping the WebSocket route in authenticate will actually require the token in the header. From there I can do the same as i do with route (get the data from the call which is made to establish the connection) and close the socket if the user is not authenticated. Will leave it here if anyone gets stuck with this.
routing {
authenticate {
webSocket("/ws") {
val principal = call.principal<JWTPrincipal>()
val email = principal?.payload?.getClaim("email")?.asString()
val expiresAt = principal?.expiresAt?.time ?: System.currentTimeMillis()
val users = userService.getAllUsers()
val randNum = Random(System.currentTimeMillis()).nextInt(0, 2)
if (randNum % 2 == 0) {
close(CloseReason(CloseReason.Codes.PROTOCOL_ERROR, "Please auth the user"))
}
incoming.consumeAsFlow()
.mapNotNull {
(it as? Frame.Text)?.readText()?.let { data ->
Json.decodeFromString<User>(data)
}
}.collect {
send(Frame.Text(userService.insertUser(it).toString()))
}
}
}
}
My client is sending the jwt token with some requests. Almost all requests is hitting a web api controller that is using the [Authorize] attribute. With this I am certain that my jwt token is properly validated, but for certain endpoints is really just want to grab the JWT token and get the sub value. I do this by using this extension method:
public static class HttpContextAccessorExtensions
{
public static string GetUserIdFromToken(this IHttpContextAccessor httpContextAccessor)
{
if (httpContextAccessor == null)
{
throw new ArgumentNullException(nameof(httpContextAccessor));
}
var context = httpContextAccessor.HttpContext;
string userId = null;
if (httpContextAccessor != null)
{
if (context != null)
{
var request = context.Request;
if (request != null)
{
request.Headers.TryGetValue("Authorization", out var bearer);
if (bearer.Any())
{
string t = bearer[0].Split(" ")[1];
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadToken(t) as JwtSecurityToken;
var utcNow = DateTime.UtcNow;
if (utcNow >= token.ValidFrom &&
utcNow <= token.ValidTo)
{
userId = token.Claims.FirstOrDefault(_ => _.Type.Equals("sub")).Value;
}
else
{
userId = String.Empty;
}
}
}
}
}
return userId;
}
}
My only problem here is that the jwt token isn't validated, so I am guessing that a "bad person" could just mingle with the valid to datetime, and keep extending the token lifetime. Please correct me if I am wrong on this.
What i want to know is: is there a way for me to validate the token? I know the JwtSecurityTokenHandler can call "ValidateToken", but this method needs a signing key, and I really dont know how to get this. I use IdentityServer 4 to generate tokens. Is there some easy way of injecting the Key into the IoC so I can get it, or is there an easy way to validate the token, that I dont know of?
Any help is appreciated
Since your access token is generated by IdentityServer4, then you should validate it using the IS4 Introspection Endpoint. This will give you the definitive answer as to if it is valid and whether or not it is still active.
Information about the Introspection Endpoint is in the IS4 docs at:
http://docs.identityserver.io/en/latest/endpoints/introspection.html
As referenced in those docs, perhaps the easiest way to interact with the endpoint is to use the IdentityModel client library (add package via NuGet), which adds the IntrospectTokenAsync() extension method to HttpClient and returns a TokenIntrospectionResponse object that has the information you need.
IdentityModel client Introspection Endpoint docs:
https://identitymodel.readthedocs.io/en/latest/client/introspection.html
Inside your controller you can just do this:
if(HttpContext.User.Identity.IsAuthenticated)
{
var claims = HttpContext.User.Claims;
}
and you can inspect the claims for whatever information is of interest.
that authenticated user identity is already validated and is available regardless of if the Authorize attribute is applied. If the user's cookie has expired etc that will return false. .
What i want to know is: is there a way for me to validate the token? I know the JwtSecurityTokenHandler can call "ValidateToken", but this method needs a signing key, and I really dont know how to get this. I use IdentityServer 4 to generate tokens.
When validating JWT token issued by Identity Server(using key pairs to issue/validate token) , specific for validating signature . API/resource server will pull down (and might cache) your identity providers discovery document located at OIDC endpoint : https://xxx/.well-known/openid-configuration. This document contains materials that allow the resource server to validate the token ,read available keys from jwks_uri .
Using key pairs menans the JWT token which is signed by IDS4 with private key. A JWT token is a non-encrypted digitally signed JSON payload which contains different attributes (claims) to identify the user/role. The signature is the last part of the JWT and needs to be used for verification of the payload. This signature was generated with the algorithm described in the header(RS256 for example) to prevent unauthorized access. In your api , you could use public key which published by IDS4(jwks_uri) to validate the signature of the JWT token . Please refer to this document for more details about JWT token .
You can use JwtBearerAuthentication middleware or IdentityServer.AccessTokenValidation middleware will help do that process . Code sample here is for your reference . If you want to manually validating a JWT token . Click here for code sample .
My only problem here is that the jwt token isn't validated, so I am guessing that a "bad person" could just mingle with the valid to datetime, and keep extending the token lifetime.
It's not recommended to use token without validating it . Since the signature part of JWT token is encrypted/encode use (header+payload) , even someone change the claims(expire time) , it won't pass the validation since he can't know Identity Server's private key to issue a correct signature .
I want to create login and logout methods and routes. I've done already basic authentication but now I'm stuck how to continue. How should I do that, should I use sessions?
I'm using Vapor 3, Swift 4 and PostgreSQL and followed this tutorial https://medium.com/rocket-fuel/basic-authentication-with-vapor-3-c074376256c3. I'm total newbie so I appreciate a lot if you can help me!
my User model
struct User : Content, PostgreSQLModel, Parameters {
var id : Int?
private(set) var email: String
private(set) var password: String
}
extension User: BasicAuthenticatable {
static let usernameKey: WritableKeyPath<User, String> = \.email
static let passwordKey: WritableKeyPath<User, String> = \.password
}
UserController.swift, registering user.
private extension UserController {
func registerUser(_ request: Request, newUser: User) throws -> Future<HTTPResponseStatus> {
return try User.query(on: request).filter(\.email == newUser.email).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "a user with this email already exists" , identifier: nil)
}
let digest = try request.make(BCryptDigest.self)
let hashedPassword = try digest.hash(newUser.password)
let persistedUser = User(id: nil, email: newUser.email, password: hashedPassword)
return persistedUser.save(on: request).transform(to: .created)
}
}
}
So in Basic authentication there is no 'logout' per se as there's no login. With HTTP Basic Auth you transmit the user's credentials with each request and validate those credentials with each request.
You mention sessions, but first it's important to know what type of service you are providing? Are you providing an API or a website? They are different use cases and have different (usually) methods for authentication and login.
For an API you can use Basic Authentication and generally in your login function you exchange the credentials for some sort of token. Clients then provide that token with future requests to authenticate the user. To log out you simply destroy the token in the backend so it is no longer valid.
For a website, things are a little different since you can't manipulate the requests like you can with a normal client (such as setting the Authorization header in the request). HTTP Basic authentication is possible in a website, though rarely used these days. What traditionally happens is you submit the user's credentials through a web form, authenticate them and then save the authenticated user in a session and provide a session cookie back to the browser. This authenticates the user in future requests. To log a user out you just remove the user from the session.
Vapor's Auth package provides everything you need to do both of these scenarios. See https://github.com/raywenderlich/vapor-til for examples of both
How would I implement the following scenario using ServiceStack?
Initial request goes to http://localhost/auth having an Authorization header defined like this:
Authorization: Basic skdjflsdkfj=
The IAuthProvider implementation validates against a user store and returns a session token as a response body (JSON).
The client uses this token an resends it against the subsequent requests like http://localhost/json/reply/orders using the Authorization header like this:
Authorization: BasicToken <TokenFromPreviousSuccessfulLogin>
Using a AuthenticateAttribute I want to flag my Service to use Authentication.
How should I implement the validation of the token for the subsequent requests?
How should I implement the IAuthProvider to provide the token?
How would I register the Providers etc.? Using RequestFilters or using the AuthFeature?
ServiceStack's JWT AuthProvider and API Key AuthProvider both use token based Authentication.
Otherwise the BasicAuth Provider is a very simple class that simply extracts the UserName and Password from the BasicAuth Header and evaluates it:
public override object Authenticate(...)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
if (basicAuth == null)
throw HttpError.Unauthorized("Invalid BasicAuth credentials");
var userName = basicAuth.Value.Key;
var password = basicAuth.Value.Value;
return Authenticate(authService, session, userName, password, request.Continue);
}
If you want to provide enhanced behavior, I would simply inherit this class check for the Authorization: BasicToken header, if it exists use that otherwise call the base.Authenticate(...) method to perform the initial validation.